
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#include <base/d3d.h>
#include <scene/SceneNode.h>

inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }

LPDIRECT3D9 _pD3D;
long _mode_count;
D3DCAPS9 * _pDevCaps;
D3DDEVTYPE _device_type;
D3DDISPLAYMODE _desktop_mode;
D3DDISPLAYMODE * _display_modes;
std::vector<Device*> _devices;

// initialize d3d!
long InitD3D(){

  _pD3D = Direct3DCreate9(D3D_SDK_VERSION);
  _mode_count = _pD3D->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);
  _display_modes = new D3DDISPLAYMODE[_mode_count];
  for(long i = 0; i < _mode_count; i++){
		_pD3D->EnumAdapterModes(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8, i, &_display_modes[i]);
	}

  _pDevCaps = new D3DCAPS9;
	if(_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _pDevCaps)){
    if(_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, _pDevCaps)){
      delete _pDevCaps;
      _pDevCaps = NULL;
    }else{
      _device_type = D3DDEVTYPE_REF;
    }
  }else{
    _device_type = D3DDEVTYPE_HAL;
  }

  _pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &(_desktop_mode));

	return 0;
}

long Device::createWindowed(HWND window, float rwidth, float rheight){
	ZeroMemory(&_d3dpp, sizeof(D3DPRESENT_PARAMETERS));
	_d3dpp.Windowed = true;
	_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	_d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
	_d3dpp.EnableAutoDepthStencil = true;
	_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;

  return create(window, rwidth, rheight);
}

long Device::createFullscreen(HWND window, unsigned long width, unsigned long height, unsigned long refresh, float rwidth, float rheight){
  _current_mode.Width = width;
  _current_mode.Height = height;
  _current_mode.RefreshRate = refresh;
  
  ZeroMemory(&_d3dpp, sizeof(D3DPRESENT_PARAMETERS));
	_d3dpp.Windowed = false;
	_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
	_d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
	_d3dpp.EnableAutoDepthStencil = true;
	_d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8;
	_d3dpp.FullScreen_RefreshRateInHz = _current_mode.RefreshRate;
	_d3dpp.BackBufferWidth = _current_mode.Width;
	_d3dpp.BackBufferHeight = _current_mode.Height;

  return create(window, rwidth, rheight);
}

long Device::create(HWND window, float rwidth, float rheight){
  _pD3D->CreateDevice(D3DADAPTER_DEFAULT, _device_type, window, D3DCREATE_HARDWARE_VERTEXPROCESSING, &_d3dpp, &_pD3Ddev);
	if(_pD3Ddev == NULL)
		_pD3D->CreateDevice(D3DADAPTER_DEFAULT, _device_type, window, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &_d3dpp, &_pD3Ddev);
	if(_pD3Ddev == NULL)
	{
		return -1;
	}

  resize(rwidth, rheight);
      
  _view._11 = 1; _view._12 = 0; _view._13 =   0; _view._14 = 0;
  _view._21 = 0; _view._22 = 1; _view._23 =   0; _view._24 = 0;
  _view._31 = 0; _view._32 = 0; _view._33 =  -1; _view._34 = 0;
  _view._41 = 0; _view._42 = 0; _view._43 = 0.0f; _view._44 = 1.0f;

  D3DXCreateMatrixStack(0, &_world);
  _world->Push();
  _world->LoadIdentity();

  InitializeCriticalSection(&cs_switch);
  InitializeCriticalSection(&cs_buffer[0]);
  InitializeCriticalSection(&cs_buffer[1]);
  laststamp = 0;
  read[0] = NULL;
  read[1] = NULL;
  timestamp[0] = 0;
  timestamp[1] = 0;

  simple_vertex *pVerts;
	_pD3Ddev->CreateVertexBuffer(4 * sizeof(simple_vertex), 0, SIMPLE_VERTEX_FORMAT, D3DPOOL_MANAGED, &_rect_verts, NULL);

	_rect_verts->Lock(0, 4 * sizeof(simple_vertex), (void **) &pVerts, 0);

	pVerts[0].x = -0.5; pVerts[0].y = -0.5; pVerts[0].z = 0; 
	pVerts[1].x = -0.5; pVerts[1].y =  0.5; pVerts[1].z = 0; 
	pVerts[2].x =  0.5; pVerts[2].y = -0.5; pVerts[2].z = 0; 
	pVerts[3].x =  0.5; pVerts[3].y =  0.5; pVerts[3].z = 0; 

	pVerts[0].tu = 0; pVerts[0].tv = 1; pVerts[0].tw = 1; pVerts[0].color = 0xffffffff;
	pVerts[1].tu = 0; pVerts[1].tv = 0; pVerts[1].tw = 1; pVerts[1].color = 0xffffffff;
	pVerts[2].tu = 1; pVerts[2].tv = 1; pVerts[2].tw = 1; pVerts[2].color = 0xffffffff;
	pVerts[3].tu = 1; pVerts[3].tv = 0; pVerts[3].tw = 1; pVerts[3].color = 0xffffffff;

	_rect_verts->Unlock();

	_pD3Ddev->CreateVertexBuffer(2 * sizeof(simple_vertex), 0, SIMPLE_VERTEX_FORMAT, D3DPOOL_MANAGED, &_line_verts, NULL);

	_line_verts->Lock(0, 4 * sizeof(simple_vertex), (void **) &pVerts, 0);

	pVerts[0].x = 0.0; pVerts[0].y = 0.0; pVerts[0].z = 0; 
	pVerts[1].x = 1.0; pVerts[1].y =  1.0; pVerts[1].z = 0;

	pVerts[0].tu = 0; pVerts[0].tv = 0; pVerts[0].tw = 1; pVerts[0].color = 0xffffffff;
	pVerts[1].tu = 1; pVerts[1].tv = 0; pVerts[1].tw = 1; pVerts[1].color = 0xffffffff;

	_line_verts->Unlock();

  _background_color = 0xff000000;

  set();
	return 0;
}

void Device::destroy(){
    DeleteCriticalSection(&cs_switch);
    DeleteCriticalSection(&cs_buffer[0]);
    DeleteCriticalSection(&cs_buffer[1]);
    if(read[0])
      read[0]->destroyGraph();
    if(read[1])
      read[1]->destroyGraph();

  _world->Release();
  _rect_verts->Release();
  _line_verts->Release();
  for(unsigned int i = 0; i < _fonts.size(); i++){
    _fonts[i]->Release();
  }
  _pD3Ddev->Release();
}

void Device::resize(float width, float height){
  _rwidth = width;
  _rheight = height;
  D3DXMatrixOrthoRH(&_projection, width, height, -100.0f, 100.0f);
  _pD3Ddev->SetTransform(D3DTS_PROJECTION, &_projection);
}

HRESULT Device::reset(long width, long height){
  if(width > 0){
    _d3dpp.BackBufferWidth = width;
    _d3dpp.BackBufferHeight = height;
  }
  for(unsigned int i = 0; i < _fonts.size(); i++){
    _fonts[i]->OnLostDevice();
  }
  HRESULT hr = _pD3Ddev->Reset(&_d3dpp);
  if(FAILED(hr))
    return hr;
  for(unsigned int i = 0; i < _fonts.size(); i++){
    _fonts[i]->OnResetDevice();
  }
  set();
  return hr;
}

unsigned long Device::deviceHeight(){
  return _d3dpp.BackBufferHeight;
}

unsigned long Device::deviceWidth(){
  return _d3dpp.BackBufferWidth;
}

void Device::set(){
  _pD3Ddev->SetFVF(SIMPLE_VERTEX_FORMAT);

  _pD3Ddev->SetRenderState(D3DRS_LIGHTING, 0);
  _pD3Ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
  _pD3Ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
  _pD3Ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, 1);
  _pD3Ddev->SetRenderState(D3DRS_ALPHAREF, (DWORD)0x00000001);
  _pD3Ddev->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); 
  _pD3Ddev->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);
  _pD3Ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
  _pD3Ddev->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

  //_pD3Ddev->SetRenderState( D3DRS_POINTSCALEENABLE,  FALSE );
  //_pD3Ddev->SetRenderState( D3DRS_POINTSIZE,     FtoDW(1.00f) );
  //_pD3Ddev->SetRenderState( D3DRS_POINTSIZE_MIN, FtoDW(0.00f) );
  //_pD3Ddev->SetRenderState( D3DRS_POINTSIZE_MAX, FtoDW(500.00f) );
  //_pD3Ddev->SetRenderState( D3DRS_POINTSCALE_A,  FtoDW(1.00f) );

  //texture stage states
  _pD3Ddev->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT3 | D3DTTFF_PROJECTED);
  _pD3Ddev->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
  _pD3Ddev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
  _pD3Ddev->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_TFACTOR);
  _pD3Ddev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);

  _pD3Ddev->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
	_pD3Ddev->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);

	//things normally taken care of every frame
	_pD3Ddev->SetStreamSource(0, _rect_verts, 0, sizeof(simple_vertex));
	_pD3Ddev->SetRenderState(D3DRS_ZFUNC, D3DCMP_ALWAYS);
	_pD3Ddev->SetTransform(D3DTS_VIEW, &_view);
  _pD3Ddev->SetTransform(D3DTS_PROJECTION, &_projection);
}

long Device::beginScene(){

	MSG message;
  long retval = 0;
  HRESULT coop = _pD3Ddev->TestCooperativeLevel();

	for(retval = -1; coop == D3DERR_DEVICELOST;)
	{
		while(coop != D3DERR_DEVICENOTRESET)
		{
			PeekMessage(&message, 0, 0, 0, PM_REMOVE);
			TranslateMessage(&message);
			DispatchMessage(&message);
			Sleep(500);
			coop = _pD3Ddev->TestCooperativeLevel();
		}
		if(FAILED(reset(-1,-1)))
			coop = D3DERR_DEVICELOST;
	}

	_pD3Ddev->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, _background_color, 1.0, 0);
	_pD3Ddev->BeginScene();

  //defaults
	_pD3Ddev->SetTexture(0, NULL);

  return retval;
}

void Device::endScene(){
	_pD3Ddev->EndScene();
	_pD3Ddev->Present(NULL, NULL, NULL, NULL);
}

void Device::setTexture(LPDIRECT3DTEXTURE9 texture){
	_pD3Ddev->SetTexture(0, texture);
  if(texture){
  _pD3Ddev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
  _pD3Ddev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
  }else{
  _pD3Ddev->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG2);
  _pD3Ddev->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2);
  }
}

LPDIRECT3DTEXTURE9 Device::getTexture(){
  LPDIRECT3DBASETEXTURE9 t;
  _pD3Ddev->GetTexture(0, &t);
  return (LPDIRECT3DTEXTURE9)t;
}

void Device::drawRect(float x, float y, float width, float height, float theta, float tu, float tv, float twidth, float theight){
	
  D3DXMATRIX texture_translate;
  D3DXMATRIX texture_scale;

  D3DXMATRIX scale;
  D3DXMATRIX rotate;
  D3DXMATRIX translate;

	_pD3Ddev->SetStreamSource(0, _rect_verts, 0, sizeof(simple_vertex));

	D3DXMatrixTranslation(&texture_translate, tu, tv, 0);
  D3DXMatrixScaling(&texture_scale, twidth, theight, 1.0);
	_pD3Ddev->SetTransform(D3DTS_TEXTURE0, &(texture_scale * texture_translate));

  D3DXMatrixScaling(&scale, width, height, 1.0);
	D3DXMatrixRotationZ(&rotate, theta);
	D3DXMatrixTranslation(&translate, x, y, 0.0);
	_pD3Ddev->SetTransform(D3DTS_WORLD, &(scale * rotate * translate * (*(_world->GetTop()))));

  _pD3Ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}

int Device::drawText(const char * text, long font, long x, long y, long width, long height, unsigned long color, unsigned long format)
{
  D3DXVECTOR2 pos;
  D3DXVECTOR4 result;
  pos.x = float(x);
  pos.y = float(y);
  D3DXVec2Transform(&result, &pos, _world->GetTop());
  RECT rect = {result.x, result.y, result.x + float(width), result.y + float(height)};
  return _fonts[font]->DrawText(NULL, text, -1, &rect, format, color);
}

void Device::drawLine(float x1, float y1, float x2, float y2){
  D3DXMATRIX scale;
  D3DXMATRIX translate;

	_pD3Ddev->SetStreamSource(0, _line_verts, 0, sizeof(simple_vertex));

  D3DXMatrixScaling(&scale, x2-x1, y2-y1, 1.0);
	D3DXMatrixTranslation(&translate, x1, y1, 0.0);
	_pD3Ddev->SetTransform(D3DTS_WORLD, &(scale * translate * (*(_world->GetTop()))));

	_pD3Ddev->SetTexture(0, NULL);
  _pD3Ddev->DrawPrimitive(D3DPT_LINESTRIP , 0, 2);
}

long Device::createFont(const char * name, unsigned long size)
{
  LPD3DXFONT font;
  if(D3DXCreateFont(_pD3Ddev, size, 0, 0, 0, 0, 0, 0, 0, 0, name, &font) != S_OK) return -1;
  font->PreloadCharacters(0, 255);
  _fonts.push_back(font);
  return _fonts.size()-1;
}

void Device::setView(float x, float y){
  _view._41 = -x; _view._42 = -y;
	_pD3Ddev->SetTransform(D3DTS_VIEW, &_view);
}

void Device::screenShot(std::string name){
	LPDIRECT3DSURFACE9 curscreen;
	std::string filename;
	HRESULT boof;

	filename = name + ".png";

	_pD3Ddev->GetRenderTarget(0, &curscreen);
	boof = D3DXSaveSurfaceToFile(filename.c_str(), D3DXIFF_PNG, curscreen, NULL, NULL);
	curscreen->Release();
}

long Device::loadTexture(LPDIRECT3DTEXTURE9 * texture, const char * file) {
	if(D3DXCreateTextureFromFile(_pD3Ddev, file, texture)==D3D_OK)
    return 0;
  else
    return -1;
}

void Device::loadShader(LPD3DXEFFECT * shader, const char * file) {
   D3DXCreateEffectFromFile(_pD3Ddev, file, NULL, NULL, 0, NULL, shader, NULL);
}

void Device::createEmptyTexture(LPDIRECT3DTEXTURE9 * texture, UINT width, UINT height) {
  D3DXCreateTexture(_pD3Ddev, width, height, D3DX_DEFAULT, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED, texture);
  D3DLOCKED_RECT locked_rect;
  (*texture)->LockRect(0, &locked_rect, NULL, 0);
  for(unsigned long i = 0; i < locked_rect.Pitch * width * height; i++) {
    ((unsigned char*)(locked_rect.pBits))[i] = 0xff;
  }
  (*texture)->UnlockRect(0);
}

void Device::setColor(unsigned long color) {
  _pD3Ddev->SetRenderState(D3DRS_TEXTUREFACTOR, color);
}

unsigned long Device::getColor(){
  unsigned long value;
  _pD3Ddev->GetRenderState(D3DRS_TEXTUREFACTOR, &value);
  return value;
}

void Device::setBackgroundColor(unsigned long color){
  _background_color = color;
}

long DestroyD3D() {
  for(int i = 0; i < _devices.size(); i++){
    if(_devices[i]){
      RemoveDevice(i);
    }
  }
  delete(_pDevCaps);
  delete(_display_modes);
  _pD3D->Release();
  return 0;
}

HRESULT Device::pushWorldMatrix(const D3DXMATRIX * matrix) {
  HRESULT hr = _world->Push();
  if(FAILED(hr))
    return hr;
  return _world->MultMatrix(matrix);
}

HRESULT Device::popWorldMatrix() {
  return _world->Pop();
}

long AddDevice(){
  for(int i = 0; i < _devices.size(); i++){
    if(_devices[i] == NULL){
      _devices[i] = new Device();
      return i;
    }
  }
  _devices.push_back(new Device());
  return _devices.size()-1;
}

void RemoveDevice(long device){
  if(_devices[device]) {
    _devices[device]->destroy();
    _devices[device] = NULL;
  }
}

Device * GetDevice(long device){
  if(device < _devices.size())
    return _devices[device];
  else
    return NULL;
}

